/*
* "doctothumb" program converts the page(s) of a document into jpeg image files
* 
* Copyright (C) 2013 Alin Dumitrescu
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*
* Distributed under the GNU GPL v2. For full terms see the file Copying or
* License URL at: http://www.gnu.org/licenses/gpl.html GPL version 2
*/

#include <windows.h>
#include "../xpdf-3.03/goo/gmem.h"
#include "../xpdf-3.03/goo/GString.h"
#include "../xpdf-3.03/xpdf/Object.h"
#include "../xpdf-3.03/xpdf/PDFDoc.h"
#include "../xpdf-3.03/splash/SplashBitmap.h"
#include "../xpdf-3.03/splash/Splash.h"
#include "../xpdf-3.03/xpdf/SplashOutputDev.h"
#include "../xpdf-3.03/xpdf/config.h"
#include "../FreeImage/Dist/FreeImage.h"
#include "doctothumb.h"

SplashColor SCMMono;
SplashColor SCMColor;
extern int volatile MaxThreads;

void PDFProcessStartRender(PCommandRender pRCR)
{
    HANDLE Hnd;
    PCommandExecute pCE = (PCommandExecute)malloc(sizeof(CommandExecute));                    //make room for the task (size of Destination*)
    ZeroMemory(pCE,sizeof(CommandExecute));
    CopyMemory(pCE,pRCR,sizeof(CommandRender));                                               //*POD layout is the same on struct inherit (size of Source)
    Hnd = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&PDFThreadedStartRender,pCE,0,NULL);    //thread it to release the message pump
    if(Hnd)
        CloseHandle(Hnd);
    else
        PostResult(-3,pRCR);//post "out of resources"
}

void __stdcall PDFThreadedStartRender(PCommandExecute pCR)
{
    __try
    {
        PDFRenderProcess(pCR);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        PostResult(-2,pCR);//post "error exception"
    }
    ExitThread( 0);
}

inline MTSharedDoc* FirstRunReusePDFObject(MTSharedDoc**List, DWORD ThreadId, PDFDoc* Source)
{
    MTSharedDoc**pItem = List;
    *pItem = new MTSharedDoc(Source);
    (*pItem)->Owned = ThreadId;
    return *pItem;
}

inline MTSharedDoc* FindFreePDF(MTSharedDoc**List, DWORD ThreadId, char* Filename)
{
    MTSharedDoc**pItem = List;
    for(int i=0;i<MaxThreads;i++)
    {
        if(!(*pItem))
        {
            *pItem = new MTSharedDoc(new PDFDoc(new GString(Filename)));
            (*pItem)->Owned = ThreadId;
            break;
        }
        else
        {
            UINT res = InterlockedCompareExchange(&(*pItem)->Owned,ThreadId,0);
            if (!res)
                break;
            else
                pItem++;
        }
    }
    return pItem ? *pItem : NULL;
}

void PDFRenderProcess(PCommandExecute pCE)
{
    char* RandomFiles = NULL;
    char* OrderedFiles = NULL;
    int buffsize = 0;
    GString* filename = NULL;
    PDFDoc* doc = NULL;
    SplashOutputDev* splashOut = NULL;
    DWORD ThreadId;
    try
    {
        doc = new PDFDoc(new GString(pCE->InFilename));                   //Load doc into a new object (PDFDoc frees the input filename)
        int Page = pCE->Pages;                                            //Store the requested page
        if (doc->isOk())
            if (doc->isEncrypted())
                pCE->Pages = -1;                                          //dont load encrypted documents, set result to -1
            else
                pCE->Pages = doc->getNumPages();                          //store page count
        else
            pCE->Pages = 0;                                               //dont allow processing of poor documents, set result to 0
        if (pCE->Pages < 1)
        {
            PostResult(pCE->Pages,pCE);                                   //notify parent process about a PDF document error
            return;
        }
        if (pCE->Pages == 1)
            Page = 1;
        if (Page > 0)
        {                                                                 //(*sth)
            OrderedFiles = (char*)malloc(MAX_PATH + 1);                   //make room for the output file + the end of string NULL char
            int bpp;
            switch(((PPDF_Context)pCE->pContext)->cm)                     //Convert the BW,Gray,RGB,BGR
            {                                                             //to a real pixel depth
            case splashModeMono1:
                bpp = 1;
                break;
            case splashModeMono8:
                bpp = 8;
                break;
            default:
                bpp = 24;
            }
            pCE->Docs = NULL;
            splashOut = new SplashOutputDev(((PPDF_Context)pCE->pContext)->cm, 1, gFalse, *((PPDF_Context)pCE->pContext)->pSC); //Create a new OutputDev
            splashOut->startDoc(doc->getXRef());                                                                                //Set OutputDev for document
            doc->displayPage(splashOut,    Page,    pCE->pContext->Resolution,
                pCE->pContext->Resolution, 0, gFalse, gTrue, gFalse);                                                           //Render that page
            CreateUniqueFile( 8, pCE->OutFolder, OrderedFiles);
//            sprintf_s(OrderedFiles,MAX_PATH,"%s\\%.6d.jpg",pCE->OutFolder,Page);
            PDFProcessSavePage(bpp,splashOut,OrderedFiles,pCE->pContext->Quality);                                              //Save the file
        }
        else
        {
            buffsize = pCE->Pages * (MAX_PATH + 2 + 1);                      //max path of a file + CRLF + the end of string NULL char
            RandomFiles = (char*)malloc(buffsize);                           //make room for the files
            pCE->Threads = 0;
            pCE->Files = RandomFiles;                                        //set the filenames buffer
            pCE->OutputRealSize = 0;
            pCE->Docs=(MTSharedDoc**)malloc(
                (MaxThreads + 1)*sizeof(MTSharedDoc*));                      //make room for the shared objects
            ZeroMemory(pCE->Docs,(MaxThreads + 1)*sizeof(MTSharedDoc*));
            PPDFRenderContext pRC = NULL;
            for(int page = 1; page <= pCE->Pages; page++)
            {
                while(pCE->Threads>=MaxThreads)                                  //if document has a lot of pages dont
                    Sleep(10);                                                  //kill overall performance (thread starvation)
                if(!pRC)                                                        //(loop-1)a: see comment on (loop-1)d
                {
                    pRC = (PPDFRenderContext)malloc(sizeof(PDFRenderContext));  //(loop-1)b: allocate a new memory block for the thread parameter
                    pRC->CmdExec = pCE;                                                    
                    pRC->Page = page;
                }
                HANDLE Hnd = CreateThread(NULL,0,
                    (LPTHREAD_START_ROUTINE)&PDFRenderPage, pRC,CREATE_SUSPENDED,&ThreadId); //thread the rendering
                if(Hnd)
                {
                    MTSharedDoc* shdoc;
                    if(page==1)    //note that meanwhile the first page thread (1) renders, reusing the opened document,
                                //for the next run of the loop, this thread (RenderProcess) will create and
                                //load one more time the same document for the next page rendering thread (2)
                        shdoc = FirstRunReusePDFObject(pCE->Docs,ThreadId,doc);      //(1)
                    else
                        shdoc = FindFreePDF(pCE->Docs,ThreadId,pCE->InFilename);     //(2)
                    pRC->Doc = shdoc->PDF();                                            //pass the opened pdf
                    pRC->OwnedPtr = &shdoc->Owned;                                      //pass the thread id reference
                    InterlockedIncrement(&pCE->Threads);                                //thread created, increment the thread count
                    ResumeThread(Hnd);                                                  //run it
                    CloseHandle(Hnd);
                    pRC = NULL;                                                         //(loop-1)c: if the thread was created set to NULL and
                }                                                                       //"go to" (loop-1)b
                else
                {                                                                       //(loop-1)d: if the thread was not created
                    Sleep(100);                                                         //take a nap and reuse the already allocated (at (loop-1)b)
                    page--;                                                             //pRC memory block for the next run, and
                }                                                                       //for the same page
            }
            while (pCE->Threads>0)                                                      //wait for all threads to end
                Sleep(10);
            doc = NULL;                                                                 //prevent double destroy: see (1)
            buffsize = pCE->OutputRealSize + 1;                                         //real buffersize + NULL terminator
            OrderedFiles = (char*)malloc(buffsize);                                     //make room for the output files
            char *pSrc, *pDest;
            pSrc  = RandomFiles;
            pDest = OrderedFiles;
            for(int page = 0; page < pCE->Pages; page++)
            {
                int len = strlen( pSrc);                                                //length of the text
                strcpy(pDest,pSrc);                                                     //copy to the destination buffer
                pDest+=len;                                                             //reposition destination after the CRLF of the source
                pSrc += (MAX_PATH + 2 + 1);                                             //get the next page
            }
            OrderedFiles[buffsize-1] = 0;                                               //terminate the buffer
        }
        PostResult(1,pCE,OrderedFiles);                                                 //post results to parent application
    }
    catch(...)
    {
        PostResult(-2,pCE);//post error
    }
    if(splashOut)                                                                       //cleanup
        delete splashOut;
    if(doc)
        delete doc;
    if(pCE->Docs)
    {
        for(int i=0;i<MaxThreads;i++)
        {
            MTSharedDoc* doc = *((MTSharedDoc**)(pCE->Docs + i));
            if(doc)
                delete doc;
        }
        free((void*)pCE->Docs);
    }
    free((void*)pCE);
    if(RandomFiles)
        free((void*)RandomFiles);
    if(OrderedFiles)
        free((void*)OrderedFiles);
}

void __stdcall PDFRenderPage(PPDFRenderContext pRC)
{
    __try
    {
        PDFProcessPage(pRC);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //Should add a per thread error queue in the next release
    }
    long* pThreads = &pRC->CmdExec->Threads;            //Grab the address of the Threads variable
    free((void*)pRC);                                   //Free the input parameter (allocated in the for-loop)
    InterlockedDecrement(pThreads);                     //Say to the Main thread we're done here
    ExitThread(0);
}

void PDFProcessPage(PPDFRenderContext pRC)
{
    char Filename[MAX_PATH];
    SplashOutputDev* splashOut = NULL;                  //same code as described above, see the (*sth) comment
    try
    {
        int bpp;
        switch(((PPDF_Context)pRC->CmdExec->pContext)->cm)
        {
        case splashModeMono1:
            bpp = 1;
            break;
        case splashModeMono8:
            bpp = 8;
            break;
        default:
            bpp = 24;
        }
        splashOut = new SplashOutputDev(((PPDF_Context)pRC->CmdExec->pContext)->cm,
                1, gFalse, *((PPDF_Context)pRC->CmdExec->pContext)->pSC);
        splashOut->startDoc(pRC->Doc->getXRef());
        pRC->Doc->displayPage(splashOut, pRC->Page,    pRC->CmdExec->pContext->Resolution,
            pRC->CmdExec->pContext->Resolution, 0, gFalse, gTrue, gFalse);
        CreateUniqueFile( 8, pRC->CmdExec->OutFolder, Filename);
//        sprintf_s(Filename,MAX_PATH-1,"%s\\%.6d.jpg",pRC->CmdExec->OutFolder,pRC->Page);
        PDFProcessSavePage(bpp,splashOut,Filename,pRC->CmdExec->pContext->Quality);
        PDFAddFileToList(pRC->CmdExec,Filename,pRC->Page);                                        //Add the name to the output filename list
    }
    catch(...)
    {
        //Should add a per thread error queue in the next release
    }
    if(splashOut)
        delete splashOut;
    InterlockedExchange(pRC->OwnedPtr,0);
//    if(pRC->Doc)
//        delete pRC->Doc;
}

void PDFProcessSavePage(int bpp, SplashOutputDev* splashOut, char* OutFilename, int Quality)
{
    FIBITMAP* fbmp = NULL;
    __try
    {
        int width = splashOut->getBitmap()->getWidth();         //Grab width in pixels
        int height = splashOut->getBitmap()->getHeight();       //Grab height in pixels
        int line = splashOut->getBitmap()->getRowSize();        //Grab a line size in bytes
        void* data = splashOut->getBitmap()->getDataPtr();      //Get a data reference
        fbmp = FreeImage_Allocate( width, height, bpp);         //Create a new memory image
        BYTE* pSrc = (BYTE*)data;
        BYTE* pDest = NULL;
        height--;
        for(int y = 0; y <= height; y++)                        //Received image is a chunk,
        {
            pDest = FreeImage_GetScanLine(fbmp, height-y);      //an upside chunk (height-y)
            CopyMemory( pDest, pSrc, line);                     //transfer data line by line...
            pSrc += line;
        }
        FreeImage_Save(FIF_JPEG, fbmp, OutFilename, Quality);   //Save the image to disk
    }
    __finally
    {
        FreeImage_Unload(fbmp);                                 //FreeImage_Unload accepts NULLs
    }
}

void PDFAddFileToList(PCommandExecute pCE,char* Filename,int Page)
{
    long len = strlen(Filename);
    char* pEntry = pCE->Files;
    pEntry += (Page - 1)*(MAX_PATH + 2 + 1);             //position the entry in the output buffer
    strcpy_s(pEntry,MAX_PATH,Filename);                  //just copy to the buffer
    pEntry[len+2] = 0;                                   //put the NULL terminator
    pEntry+=len;
    strcpy(pEntry,"\r\n");                               //add CRFL after the filename
    len+=2;                                              //dont include the NULL string terminator but make room for the CRLF
    InterlockedExchangeAdd(&pCE->OutputRealSize,len);    //increment the output buffer to send to parent application
}
